#include "my_slru.h"
#include "my_log.h"
#include "my_thread_base.h"

typedef struct {
    my_rbtree_node_t        tree_node;
    my_queue_t              queue;
    int                     idx;
    my_slru_t               *slru;
    void                    *key;
    void                    *val;
} my_slru_item_t;


static inline my_slru_item_t *
my_slru_item_create(my_slru_t *slru, void *key, void *val)
{
    my_slru_item_t *item;
    item = my_thread_calloc(sizeof(my_slru_item_t));
    item->slru = slru;
    item->key = key;
    item->val = val;
    return item;
}


static inline void
my_slru_item_destroy(my_slru_item_t *item)
{
    my_thread_free(item);
}


static inline int
my_slru_item_cmp(my_rbtree_node_t *a, my_rbtree_node_t *b)
{
    my_slru_item_t *anode, *bnode;
    anode = (my_slru_item_t *)a;
    bnode = (my_slru_item_t *)b;
    return anode->slru->key_cmp_handler(anode->key, bnode->key);
}


my_slru_t *
my_slru_create(
    int64_t nlist,
    int64_t max_item,
    int64_t max_bytes,
    my_slru_item_size_get_handler_t item_size_get_handler,
    my_slru_item_free_handler_t free_handler,
    my_slru_item_key_cmp_t      key_cmp_handler)
{
    if (nlist <= 0) {
        MY_LOG_FATAL("nlist must be greate than 0");
    }

    max_item /= nlist;
    max_bytes /= nlist;

    if (max_item <= 0 && max_bytes <= 0) {
        MY_LOG_FATAL("max_item and max_bytes must have one greate than 0");        
    }

    if (max_bytes > 0 && item_size_get_handler == NULL) {
        MY_LOG_FATAL("item_size_get_handler cannot be nil when max_bytes greate than 0");
    }

    if (key_cmp_handler == NULL) {
        MY_LOG_FATAL("key_cmp_handler cannot be null");
    }

    my_slru_t *slru;
    slru = my_thread_calloc(sizeof(my_slru_t));
    slru->nlist     = nlist;
    slru->max_item  = max_item;
    slru->max_bytes = max_bytes;
    slru->item_size_get_handler = item_size_get_handler;
    slru->free_handler = free_handler;
    slru->key_cmp_handler = key_cmp_handler;

    my_queue_t *lists = my_thread_calloc((int64_t)sizeof(my_queue_t) * nlist);
    for (int64_t i = 0; i < nlist; i++) {
        my_queue_init(lists + i);
    }

    int64_t *lists_bytes = my_thread_calloc((int64_t)sizeof(int64_t) * nlist);
    int64_t *lists_n = my_thread_calloc((int64_t)sizeof(int64_t) * nlist);

    slru->lists = lists;
    slru->lists_bytes = lists_bytes;
    slru->lists_n = lists_n;

    my_rbtree_init(&slru->tree,
        &slru->sentinel,
        my_slru_item_cmp);

    return slru;
}


void
my_slru_destroy(my_slru_t *slru)
{
    my_queue_t *list, *q;
    my_slru_item_t *item;

    for (int i = 0; i < slru->nlist; i++) {
        list = slru->lists + i;
        while (!my_queue_empty(list)) {
            q = my_queue_last(list);
            my_queue_remove(q);
            item = my_queue_data(q, my_slru_item_t, queue);
            my_rbtree_delete(&slru->tree, &item->tree_node);
            if (slru->free_handler) {
                slru->free_handler(item->key, item->val);
            }
            my_slru_item_destroy(item);
        }
    }

    my_thread_free(slru->lists_n);
    my_thread_free(slru->lists_bytes);
    my_thread_free(slru->lists);
}


static my_bool_t
my_slru_list_is_full(my_slru_t *slru, int64_t list_idx)
{
    if (slru->max_item > 0 &&
        *(slru->lists_n + list_idx) >= slru->max_item) {
        return MY_TRUE;
    }

    if (slru->max_bytes > 0 &&
        *(slru->lists_bytes + list_idx) >= slru->max_bytes) {
        return MY_TRUE;
    }

    return MY_FALSE;
}


void *
my_slru_get(my_slru_t *slru, void *key, my_bool_t *found)
{
    if (found) {
        *found = MY_FALSE;
    }

    my_slru_item_t target = {.key = key};
    my_rbtree_node_t *node = my_rbtree_lookup(&slru->tree, &target.tree_node);
    if (!node) {
        return NULL;
    }

    if (found) {
        *found = MY_TRUE;
    }

    my_slru_item_t *item = (my_slru_item_t *)node;

    // already on fainal lists, move to head.
    if (item->idx == slru->nlist - 1) {
        my_queue_remove(&item->queue);
        my_queue_insert_head(slru->lists + item->idx, &item->queue);
        return item->val;
    }

    int64_t item_size = 0;
    if (slru->item_size_get_handler != NULL) {
        item_size = slru->item_size_get_handler(item->key, item->val);
    }
    *(slru->lists_bytes + item->idx) -= item_size;
    *(slru->lists_n + item->idx) -= 1;
    my_queue_remove(&item->queue);
    item->idx += 1;

    if (!my_slru_list_is_full(slru, item->idx)) {
        *(slru->lists_bytes + item->idx) += item_size;
        *(slru->lists_n + item->idx) += 1;
        my_queue_insert_head(slru->lists + item->idx, &item->queue);
        return item->val;
    }

    // current list is full, swap.
    my_queue_t *back_q = my_queue_last(slru->lists + item->idx);
    my_slru_item_t *back_item = my_queue_data(back_q, my_slru_item_t, queue);
    my_queue_remove(back_q);
    int64_t back_item_size = 0;
    if (slru->item_size_get_handler != NULL) {
        back_item_size = slru->item_size_get_handler(back_item->key, back_item->val);
    }
    *(slru->lists_bytes + back_item->idx) -= back_item_size;
    *(slru->lists_n + back_item->idx) -= 1;
    back_item->idx -= 1;
    *(slru->lists_bytes + back_item->idx) += back_item_size;
    *(slru->lists_n + back_item->idx) += 1;
    my_queue_insert_head(slru->lists + back_item->idx, &back_item->queue);

    *(slru->lists_bytes + item->idx) += item_size;
    *(slru->lists_n + item->idx) += 1;
    my_queue_insert_head(slru->lists + item->idx, &item->queue);

    return item->val;
}


static my_bool_t
my_slru_cache_remove_helper(my_slru_t *slru, void *key)
{
    my_slru_item_t target = {.key = key};
    my_rbtree_node_t *node = my_rbtree_lookup(&slru->tree, &target.tree_node);
    if (!node) {
        return MY_FALSE;
    }

    my_slru_item_t *item = (my_slru_item_t *)node;
    int64_t item_size = 0;
    if (slru->item_size_get_handler) {
        item_size = slru->item_size_get_handler(item->key, item->val);
    }

    *(slru->lists_n + item->idx) -= 1;
    *(slru->lists_bytes + item->idx) -= item_size;

    my_queue_remove(&item->queue);
    my_rbtree_delete(&slru->tree, &item->tree_node);

    if (slru->free_handler) {
        slru->free_handler(item->key, item->val);
    }
    my_slru_item_destroy(item);

    return MY_TRUE;
}


void
my_slru_put(my_slru_t *slru, void *key, void *val)
{
    // remove old key value.
    my_slru_cache_remove_helper(slru, key);

    my_slru_item_t *item = my_slru_item_create(slru, key, val);
    int64_t item_size = 0;
    if (slru->item_size_get_handler != NULL) {
        item_size = slru->item_size_get_handler(item->key, item->val);
    }

    if (my_slru_list_is_full(slru, 0)) {
        my_queue_t *back_q = my_queue_last(slru->lists + 0);
        my_slru_item_t *back_item = my_queue_data(back_q, my_slru_item_t, queue);
        int64_t back_item_size = 0;
        if (slru->item_size_get_handler != NULL) {
            back_item_size = slru->item_size_get_handler(back_item->key, back_item->val);
        }
        *(slru->lists_n + 0) -= 1;
        *(slru->lists_bytes + 0) -= back_item_size;
        my_queue_remove(&back_item->queue);
        my_rbtree_delete(&slru->tree, &back_item->tree_node);
        if (slru->free_handler) {
            slru->free_handler(back_item->key, back_item->val);
        }
        my_slru_item_destroy(back_item);
    }

    *(slru->lists_n + 0) += 1;
    *(slru->lists_bytes + 0) += item_size;
    my_rbtree_insert(&slru->tree, &item->tree_node);
    my_queue_insert_head(slru->lists + 0, &item->queue);
}


my_bool_t
my_slru_remove(my_slru_t *slru, void *key)
{
    return my_slru_cache_remove_helper(slru, key);
}


// will not increase the heat of the object
void *
my_slru_ge(my_slru_t *slru, void *key, my_bool_t *found)
{
    if (found) {
        *found = MY_FALSE;
    }

    my_slru_item_t target = {.key = key};
    my_rbtree_node_t *node =  my_rbtree_ge(&slru->tree, &target.tree_node);
    if (!node) {
        return NULL;
    }

    if (found) {
        *found = MY_TRUE;
    }

    my_slru_item_t *item = (my_slru_item_t *)node;
    return item->val;
}


// will not increase the heat of the object
void *
my_slru_le(my_slru_t *slru, void *key, my_bool_t *found)
{
    if (found) {
        *found = MY_FALSE;
    }

    my_slru_item_t target = {.key = key};
    my_rbtree_node_t *node =  my_rbtree_le(&slru->tree, &target.tree_node);
    if (!node) {
        return NULL;
    }

    if (found) {
        *found = MY_TRUE;
    }

    my_slru_item_t *item = (my_slru_item_t *)node;
    return item->val;
}